import { Next } from "koa";
import { AppCTX } from "../types/koaExtend";
import { isUndefined, upperCase, rangeNum, dateFormat, isEmpty } from "mmb";
import {
  makeRouteRLUserKey,
  incrCache,
  mgetCache,
  expireCache,
} from "../proxy/redisProxy";

export enum SlideWindowInterval {
  DAY = "day",
  HOUR = "hour",
  MIN = "minute",
  SEC = "second",
}

enum RateLimitType {
  BUCKET = "bucket",
  SLIDE_WINDOW = "slide_window",
}

export interface SlideWindowOptions {
  interval: SlideWindowInterval; // 间隔
  rangeSize: number; //
}

const defaultSlideWindowOptions: SlideWindowOptions = {
  interval: SlideWindowInterval.MIN, // 间隔
  rangeSize: 10, // 往前计算多少个间隔
};

/**
 * 计算多个时间窗口
 */
const makeLastSlideWindows = ({
  interval,
  rangeSize,
}: SlideWindowOptions): string[] => {
  const ret = rangeNum(0, 0 - rangeSize, -1).map((n) => {
    let now = new Date();
    let d: Date | undefined = undefined;
    let v: string = "";
    // console.log("n:", now.getMinutes(), n);
    switch (interval) {
      case SlideWindowInterval.DAY:
        d = new Date(now.setDate(now.getDate() + n));
        v = dateFormat(d!, "yyyyMMdd");
        break;
      case SlideWindowInterval.MIN:
        d = new Date(now.setHours(now.getHours() + n));
        v = dateFormat(d!, "yyyyMMddHH");
        break;
      case SlideWindowInterval.MIN:
        d = new Date(now.setMinutes(now.getMinutes() + n));
        v = dateFormat(d!, "yyyyMMddHHmm");
        break;
      case SlideWindowInterval.SEC:
        d = new Date(now.setSeconds(now.getSeconds() + n));
        v = dateFormat(d!, "yyyyMMddHHmmss");
        break;
    }
    return v;
  });
  return ret.filter((v) => !isEmpty(v));
};

const getExpireTTLOfSlideWindow = ({
  interval,
  rangeSize,
}: SlideWindowOptions): number => {
  let now = new Date();
  let nowTS = +now;
  let d: number = 0;
  let n = 0 - rangeSize;

  switch (interval) {
    case SlideWindowInterval.DAY:
      d = now.setDate(now.getDate() + n);
      break;
    case SlideWindowInterval.MIN:
      d = now.setHours(now.getHours() + n);
      break;
    case SlideWindowInterval.MIN:
      d = now.setMinutes(now.getMinutes() + n);
      break;
    case SlideWindowInterval.SEC:
      d = now.setSeconds(now.getSeconds() + n);
      break;
  }
  const ttl = nowTS - d + 1; // 加1秒保证脱离slidewindow区间
  return Math.abs(ttl / 1000);
};

const makeSW = (method: string, url: string) => (sw: string): string => {
  return `${method}${url}:${sw}`;
};

export const createSlideWindowRateLimiter = (
  limit: number,
  options: SlideWindowOptions = defaultSlideWindowOptions,
  message: string = "api限流"
) => {
  return async (ctx: AppCTX, next: Next) => {
    const reqUrl = ctx.request.url;
    const method = upperCase(ctx.request.method);
    try {
      let userId = ctx.userId;
      if (isUndefined(userId)) {
        userId = "0";
      }
      console.log("userId=>", userId);
      const lastSlideWindows = makeLastSlideWindows(options);
      const currentWindow = lastSlideWindows[0];
      const key = makeRouteRLUserKey(method, reqUrl, userId)(currentWindow);
      const lastSlideWindowKeys = lastSlideWindows.map((sw) =>
        makeRouteRLUserKey(method, reqUrl, userId)(sw)
      );

      // 1 当前window + 1
      const v = await incrCache(key);
      // 2 设置过期时间
      console.log("v=>", v);
      if (v === 1) {
        const ttl = getExpireTTLOfSlideWindow(options);
        console.log("ttl=>", ttl);
        await expireCache(key, ttl);
      }
      
      const all = await mgetCache(...lastSlideWindowKeys).then((values) => {
        return values.reduce((total, v) => {
          return total + Number(v);
        }, 0);
      });

      console.log("all=>", all);
      console.log("limit=>", limit);
      // 2 比较过去的slideWindows和阀值
      if (all > limit) {
        throw Error(message);
      }
    } catch (error) {
      console.error("rateLimitNW err=>", error.message);
      ctx.throw(
        429,
        JSON.stringify({
          code: 429,
          message: error.message,
        })
      );
    }
    await next();
  };
};
